package cn.z2huo.demo.spring.webmvc.controller.download;

import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * <p>文件分块下载
 *
 * @author z2huo
 */
@RestController
@RequestMapping("/chunk")
@Slf4j
public class ChunkedDownloadFileController {

    @GetMapping("/download/response")
    public void downloadLargeFileRangeResponseStream(
            @RequestHeader(value = "Range", required = false) String rangeHeader,
            HttpServletResponse response
    ) {

        log.info("downloadLargeFileRangeResponseStream start");
        ClassPathResource resource = new ClassPathResource("csv/problem/NO_0003/test_data_5000000.csv");
        if (!resource.exists()) {
            return;
        }

        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.addHeader(HttpHeaders.CONTENT_ENCODING, "UTF-8");

        String fileName = URLEncoder.encode("大 csv 文件.csv", StandardCharsets.UTF_8).replace("+", "%20");
        List<HttpRange> ranges = HttpRange.parseRanges(rangeHeader);

        long fileLength;
        try {
            fileLength = resource.contentLength();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        long start = 0;
        long end = fileLength - 1;
        if (!ranges.isEmpty()) {
            response.setStatus(HttpStatus.PARTIAL_CONTENT.value());

            HttpRange range = ranges.getFirst();
            start = range.getRangeStart(fileLength);
            end = range.getRangeEnd(fileLength);
            if (end > fileLength - 1) {
                end =fileLength - 1;
            }
        }
        long rangeLength = end - start + 1;
        response.addHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(rangeLength));
        response.addHeader(HttpHeaders.CONTENT_RANGE, "bytes " + start + "-" + end + "/" + fileLength);
        response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename*=utf-8''" + fileName);

        long sum = 0;
        try (OutputStream os = new BufferedOutputStream(response.getOutputStream());
             InputStream is = new BufferedInputStream(resource.getInputStream())) {
            is.skip(start);
            byte[] buffer = new byte[1024 * 1024];
            int length;
            while (sum < rangeLength) {
                int readLength = (int) (rangeLength - sum);
                length = is.read(buffer, 0, (rangeLength - sum) <= buffer.length ? readLength : buffer.length);
                sum += length;
                os.write(buffer,0, length);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * <p>大文件分段下载，使用 HTTP Header 中的 Range
     */
    @GetMapping("/download")
    public ResponseEntity<Resource> downloadLargeFileRange(
            @RequestHeader(value = "Range", required = false) String rangeHeader
    ) throws IOException {

        log.info("downloadLargeFileRange start");

        ClassPathResource resource = new ClassPathResource("csv/problem/NO_0003/test_data_5000000.csv");

        if (!resource.exists()) {
            return ResponseEntity.notFound().build();
        }

        String fileName = "大 csv 文件.csv";
//        String fileName = URLEncoder.encode("大 csv 文件.csv", StandardCharsets.UTF_8).replace("+", "%20");

        // 解析Range头
        List<HttpRange> ranges = HttpRange.parseRanges(rangeHeader);

        if (ranges.isEmpty()) {

            log.info("no range");

            ContentDisposition contentDisposition = ContentDisposition.attachment()
                    .filename(fileName, StandardCharsets.UTF_8)
                    .build();

            HttpHeaders headers = new HttpHeaders();
            headers.set(HttpHeaders.CONTENT_ENCODING, "UTF-8");
            headers.setContentDisposition(contentDisposition);

            return ResponseEntity.ok()
                    .headers(headers)
                    .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
//                    .contentLength(resource.contentLength())
                    .body(resource);
        } else {

            log.info("range");

            long fileLength = resource.contentLength();

            HttpRange range = ranges.getFirst();
            long rangeStart = range.getRangeStart(fileLength);
            long rangeEnd = range.getRangeEnd(fileLength);
            long rangeLength = rangeEnd - rangeStart + 1;

            // readAllBytes 会读取完整的字节数组
//            InputStreamResource inputStreamResource = new InputStreamResource(new ByteArrayInputStream(
//                    resource.getInputStream().readAllBytes(), (int) rangeStart, (int) rangeLength));

            BufferedInputStream bufferedInputStream = new BufferedInputStream(resource.getInputStream());
            bufferedInputStream.skipNBytes(rangeStart);

            byte[] bytes = new byte[(int) rangeLength];
            // 这个不可以，不能决定从 inputstream 的开头读取
//            int read = resource.getInputStream().read(bytes, 0, (int) rangeLength);
            int read = bufferedInputStream.read(bytes, 0, (int) rangeLength);
            InputStreamResource inputStreamResource = new InputStreamResource(new ByteArrayInputStream(bytes));

            ContentDisposition contentDisposition = ContentDisposition.attachment()
                    .filename(fileName, StandardCharsets.UTF_8)
                    .build();

            HttpHeaders headers = new HttpHeaders();
            headers.set(HttpHeaders.CONTENT_ENCODING, "UTF-8");
            headers.setContentDisposition(contentDisposition);
            headers.set(HttpHeaders.CONTENT_RANGE, "bytes " + rangeStart + "-" + rangeEnd + "/" + fileLength);
            headers.set(HttpHeaders.CONTENT_LENGTH, String.valueOf(rangeLength));

            return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT)
                    .headers(headers)
                    .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
                    .body(inputStreamResource);
        }
    }

}
